//-------------------------------------------------------------------
//	Copyright (c) 2010-2014  Zhirnov Andrey
//	This file is part of the "UX Standard Lib" project.
//	See copyright notice in "UX_STL_Ext.h".
//-------------------------------------------------------------------

#ifndef _UXS_STRING_PARSER_H
#define _UXS_STRING_PARSER_H

#pragma once

#include "../../stl_core/ux_stl_core.h"

namespace UX_STL
{
namespace UXTypes
{

	//
	// String Parser
	//

	struct StringParser
	{
		// move cursor //
		static void ToEndOfLine		(uni_c_string str, INOUT usize &pos);
		static void ToBeginOfLine	(uni_c_string str, INOUT usize &pos);
		static void ToNextLine		(uni_c_string str, INOUT usize &pos);
		static void ToPrevLine		(uni_c_string str, INOUT usize &pos);

		static bool IsBeginOfLine	(uni_c_string str, usize pos);
		static bool IsEndOfLine		(uni_c_string str, usize pos);

		static usize CalculateNumberOfLines	(uni_c_string str);


		// Indent //
		static void IncreaceIndent	(INOUT string &str, uni_c_string indent = "\t");
		static void DecreaceIndent	(INOUT string &str, uni_c_string indent = "\t");


		// utils //
		static uint8 CharToCode (char c);


		// parser //
		struct CStyleParser
		{
		private:
			enum EMode {
				NONE,
				WORD,
				NUMBER,
				OPERATOR,
			};

			EMode	_mode;

		public:
			CStyleParser ();
			bool IsBegin (char c);
			bool IsEnd (char c);
			bool IsUnused (char c);
			bool OnUnknown (char)		{ return false; }	// return true to continue parsing, false - to exit

		private:
			static bool _IsWord (char c);
			static bool _IsNumber (char c);
			static bool _IsOperator (char c);
		};

		static void Tokenize (uni_c_string str, char divisor, OUT array< uni_c_string > &tokens);

		template <typename TParser>
		static bool DivideString (uni_c_string str, TParser parser, OUT array< uni_c_string > &tokens);

		static bool CStyleDivideString (uni_c_string str, OUT array< uni_c_string > &tokens) {
			return DivideString( str, CStyleParser(), tokens );
		}

		static bool ReadString (uni_c_string str, INOUT usize &pos, OUT uni_c_string &result);
		static bool ReadStringCStyle (uni_c_string str, INOUT usize &pos, OUT string &result);
		static void ParseCStyleString (uni_c_string str, OUT string &result);
	};
	
	
/*
=================================================
	ToEndOfLine
=================================================
*/
	inline void StringParser::ToEndOfLine (uni_c_string str, INOUT usize &pos)
	{
		while ( pos < str.Length() )
		{
			//const char	c = str[pos];
			const char	n = (pos+1) >= str.Length() ? 0 : str[pos+1];
				
			++pos;

			if ( n == '\n' or n == '\r' )
				return;
		}
	}
	
/*
=================================================
	ToBeginOfLine
=================================================
*/
	inline void StringParser::ToBeginOfLine (uni_c_string str, INOUT usize &pos)
	{
		while ( pos < str.Length() )
		{
			//const char	c = str[pos];
			const char	p = (pos-1) >= str.Length() ? 0 : str[pos-1];
				
			if ( p == '\n' or p == '\r' )
				return;

			--pos;
		}
	}
	
/*
=================================================
	IsBeginOfLine
=================================================
*/
	inline bool StringParser::IsBeginOfLine (uni_c_string str, const usize pos)
	{
		usize	p = pos;
		ToBeginOfLine( str, p );
		return p == pos;
	}
	
/*
=================================================
	IsEndOfLine
=================================================
*/
	inline bool StringParser::IsEndOfLine (uni_c_string str, const usize pos)
	{
		usize	p = pos;
		ToEndOfLine( str, p );
		return p == pos;
	}

/*
=================================================
	ToNextLine
=================================================
*/
	inline void StringParser::ToNextLine (uni_c_string str, INOUT usize &pos)
	{
		while ( pos < str.Length() )
		{
			const char	c = str[pos];
			const char	n = (pos+1) >= str.Length() ? 0 : str[pos+1];
			
			++pos;

			// windows style "\r\n"
			if ( c == '\r' and n == '\n' )
			{
				++pos;
				return;
			}

			// linux style "\n" (or mac style "\r")
			if ( c == '\n' or c == '\r' )
				return;
		}
	}
	
/*
=================================================
	ToPrevLine
=================================================
*/
	inline void StringParser::ToPrevLine (uni_c_string str, INOUT usize &pos)
	{
		while ( pos < str.Length() )
		{
			const char	c = str[pos];
			const char	p = (pos-1) >= str.Length() ? 0 : str[pos-1];
			
			--pos;

			// windows style "\r\n"
			if ( p == '\r' and c == '\n' )
			{
				--pos;
				return;
			}

			// linux style "\n" (or mac style "\r")
			if ( p == '\n' or p == '\r' )
				return;
		}
	}
	
/*
=================================================
	CalculateNumberOfLines
=================================================
*/
	inline usize StringParser::CalculateNumberOfLines (uni_c_string str)
	{
		usize lines = 0;

		for (usize pos = 0; pos < str.Length(); ++lines)
		{
			ToNextLine( str, pos );
		}
		return lines;
	}

/*
=================================================
	ReadString
----
	read string from " to "
=================================================
*/
	inline bool StringParser::ReadString (uni_c_string str, INOUT usize &pos, OUT uni_c_string &result)
	{
		for (; pos < str.Length(); ++pos)
		{
			if ( str[pos] == '"' )
				break;
		}

		CHECK_ERR( str[pos] == '"' );

		const usize	begin = ++pos;

		for (; pos < str.Length(); ++pos)
		{
			const char	c = str[pos];

			if ( c == '"' )
			{
				result = uni_c_string( str.cstr() + begin, pos - begin );
				++pos;
				return true;
			}
		}
	
		RETURN_ERR( "no pair for bracket \"" );
	}
	
/*
=================================================
	CharToCode
=================================================
*/
	inline uint8 StringParser::CharToCode (char c)
	{
		if ( c >= '0' and c <= '9' )	return c - '0';
		if ( c >= 'a' and c <= 'f' )	return c - 'a' + char(10);
		if ( c >= 'A' and c <= 'F' )	return c - 'A' + char(10);
		
		WARNING( "incorrect char" );
		return 0;
	}

/*
=================================================
	ReadStringCStyle
----
	read string from " to ", ignore \",
	convert \X and \xXX to char
=================================================
*/
	inline bool StringParser::ReadStringCStyle (uni_c_string str, INOUT usize &pos, OUT string &result)
	{
		result.ClearMem();

		for (; pos < str.Length(); ++pos)
		{
			if ( str[pos] == '"' )
				break;
		}

		CHECK_ERR( str[pos] == '"' );
		
		const usize	begin = ++pos;

		for (; pos < str.Length(); ++pos)
		{
			const char	c = str[ pos ];
	
			if ( c == '"' )
			{
				ParseCStyleString( uni_c_string( str.cstr() + begin, pos - begin ), result );
				++pos;
				return true;
			}

			if ( c == '\\' )
			{
				const char	n = str[ ++pos ];

				if ( n == '"' )
					continue;
			}
		}

		RETURN_ERR( "no pair for bracket \"" );
	}
		
/*
=================================================
	ParseCStyleString
----
	convert \X and \xXX to char
=================================================
*/
	inline void StringParser::ParseCStyleString (uni_c_string str, OUT string &result)
	{
		for (usize i = 0; i < str.Length(); ++i)
		{
			const char	c = str[i];
	
			if ( c == '\\' )
			{
				const char	n = str[ ++i ];

				switch ( n )
				{
					case 'N'  :	
					case 'n'  :		result << '\n';		break;
					case 'T'  :
					case 't'  :		result << '\t';		break;
					case 'R'  :
					case 'r'  :		result << '\r';		break;
					case 'X'  :
					case 'x'  :		result << char( ( CharToCode( str[i+1] ) << 4 ) + CharToCode( str[i+2] ) );  i += 2;  break;
					case '\\' :		result << '\\';		break;
					case '"'  :		result << '"';		break;
					case '\'' :		result << '\'';		break;
					default   :		result << "<unknown char after '\\': " << CharToCode( n ) << ">";	WARNING( "unknown symbol" );
				}
			}
			else
				result << c;
		}
	}

/*
=================================================
	Tokenize
=================================================
*/
	inline void StringParser::Tokenize (uni_c_string str, const char divisor, OUT array< uni_c_string > &tokens)
	{
		usize	begin = 0;

		tokens.ClearMem();

		for (usize i = 0; i < str.Length(); ++i)
		{
			const char	c = str[i];

			if ( c == divisor )
			{
				tokens.PushBack( uni_c_string( str.cstr() + begin, i - begin ) );
				begin = i+1;
			}
		}

		tokens.PushBack( uni_c_string( str.cstr() + begin, str.Length() - begin ) );
	}

/*
=================================================
	DivideString
=================================================
*/
	template <typename TParser>
	inline bool StringParser::DivideString (uni_c_string str, TParser parser, OUT array< uni_c_string > &tokens)
	{
		bool	word = false;
		usize	begin = 0;

		tokens.ClearMem();

		for (usize i = 0; i < str.Length(); ++i)
		{
			const char	c = str[i];

			if ( word )
			{
				if ( parser.IsEnd( c ) )
				{
					word = false;
					tokens.PushBack( uni_c_string( str.cstr() + begin, i - begin ) );
					--i;
				}
				continue;
			}

			if ( parser.IsUnused( c ) )
				continue;

			if ( parser.IsBegin( c ) )
			{
				word = true;
				begin = i;
				continue;
			}
		
			if ( not parser.OnUnknown( c ) ) {
				RETURN_ERR( "invalid char '" << c << "'" );
			}
		}	

		if ( word )
			tokens.PushBack( uni_c_string( str.cstr() + begin, str.Length() - begin ) );

		return true;
	}

/*
=================================================
	constructor
=================================================
*/
	inline StringParser::CStyleParser::CStyleParser () : _mode(NONE)
	{}
		
/*
=================================================
	IsBegin
=================================================
*/
	inline bool StringParser::CStyleParser::IsBegin (char c)
	{
		if ( _IsWord( c ) )		{ _mode = WORD;		return true; }
		if ( _IsNumber( c ) )	{ _mode = NUMBER;	return true; }
		if ( _IsOperator( c ) )	{ _mode = OPERATOR;	return true; }

		_mode = NONE;
		return false;
	}
		
/*
=================================================
	IsEnd
=================================================
*/
	inline bool StringParser::CStyleParser::IsEnd (char c)
	{
		switch ( _mode )
		{
			case WORD :
			{
				return not ( _IsWord( c ) | _IsNumber( c ) );
			}
			case NUMBER :
			{
				// TODO: correctly parse int, hex, float numbers
				return not ( _IsNumber( c ) |
						( (c >= 'A') & (c <= 'F') ) |
						( (c >= 'a') & (c <= 'f') ) |
						  (c == '.') );
			}
			case OPERATOR :
			{
				// TODO: operators with 1 and 2 symbols (example: = and == )
				return not _IsOperator( c );
			}
			case NONE :
				return true;
		}
		return true;
	}
		
/*
=================================================
	IsUnused
=================================================
*/
	inline bool StringParser::CStyleParser::IsUnused (char c)
	{
		return ( (c == ' ') | (c == '\t') );
	}
		
/*
=================================================
	_IsWord
=================================================
*/
	inline bool StringParser::CStyleParser::_IsWord (char c)
	{
		return	bool( (c >= 'A') & (c <= 'Z') ) |
				bool( (c >= 'a') & (c <= 'z') ) |
				(c == '_');
	}
			
/*
=================================================
	_IsNumber
=================================================
*/
	inline bool StringParser::CStyleParser::_IsNumber (char c)
	{
		return	( (c >= '0') & (c <= '9') );
	}

/*
=================================================
	_IsOperator
=================================================
*/
	inline bool StringParser::CStyleParser::_IsOperator (char c)
	{
		return	( (c == '-') | (c == '+') | (c == '*') | (c == '/') |
				  (c == '|') | (c == '&') | (c == '^') | (c == '<') |
				  (c == '>') | (c == '?') | (c == ':') | (c == ';') |
				  (c == ',') | (c == '.') | (c == '!') | (c == '~') |
				  (c == '[') | (c == ']') | (c == '(') | (c == ')') |
				  (c == '=') | (c == '%') | (c == '"') | (c == '\'') );
	}
	
/*
=================================================
	IncreaceIndent
=================================================
*/
	inline void StringParser::IncreaceIndent (INOUT string &str, uni_c_string indent)
	{
		ASSERT( not indent.Empty() );

		usize	pos = 0;

		while ( pos < str.Length() )
		{
			str.Insert( indent, pos );

			pos += indent.Length();

			ToNextLine( str, pos );
		}
	}
	
/*
=================================================
	DecreaceIndent
=================================================
*/
	inline void StringParser::DecreaceIndent (INOUT string &str, uni_c_string indent)
	{
		ASSERT( not indent.Empty() );

		usize	pos = 0;

		while ( pos < str.Length() )
		{
			if ( str.SubString( pos ).CmpPrefix( indent, indent.Length() ) )
			{
				str.Erase( pos, indent.Length() );
				//pos -= indent.Length();
			}

			ToNextLine( str, pos );
		}
	}


}	// UXTypes
}	// UX_STL

#endif	// _UXS_STRING_PARSER_H